# $Id: client.py 9227e81e919a 2012/08/27 15:49:57 pgurumur $
# Copyright (c) 2009 Prabhu Gurumurthy  <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

__version__  = "$Revision: 9227e81e919a $"
__author__   = "$Author: pgurumur $"
__modified__ = "$Date: 2012-08-27 15:49:57Z $"

# essential imports
import os
import shlex
import sys

# local imports
import console
import tcp

# Class error
class DictError(Exception):
   pass

# Dictionary client, inherits tcp
class DictClient(tcp.tcp):
   def __init__(self, Host, Port):
      # init tcp class
      super(DictClient, self).__init__(Host, Port)

      # init error class
      self._err = DictError()

      # uname for client information
      self._uname = os.uname()

      # Local defines
      self._cmdLine = None
      self._cmdLength = 0
      self._cmd = None
      self._connect = False

      # Defines for how much can I read and how long should I wait for 
      # timeout
      self._ReadLength = 1024
      self._ReadWait = 2

      if Host and Port:
         try:
            # Connect to the dictonary server
            self.Connect()
         except tcp.SocketError, message:
            raise DictError, message
         else:
            # At this point, I have connected
            self._connect = True
            self._cmdLine = "client info %s %s version 0.1\r\n" %(
                  self._uname[0], self._uname[2])

            # send hello to the server
            self.SockIO()

            # Set the completer for the command line
            completer = console.CmdCompleter(
                  { 'show' : [ 'databases', 'strategy', 'db' , 'strat',
                        'server', 'info' ],
                     'define': [],
                     'match' : [],
                     'help' : [],
                     'status' : [],
                     'quit' : [],
                     })

            # invoke the console
            self._console = console.CmdLine(completer)

   def Signal(self, Signal, Frame):
      # Signal handler, we ignore INT signal(2), users must use ctrl-d or
      # exit to leave the program, every other signal that we got signed
      # up for, we clos the fd, print why we did so and exit with value 1
      if Signal == 2:
         print "\nuse ctrl-d to exit"
      else:
         self.Close()
         self._connect = False
         print "program terminated with signal: %d" %Signal
         sys.exit(1)

   def _error(self, Message):
      # Fatal error, close the fd and raise error, caller is responsible
      # for failing nicely or not
      self.Close()
      if Message:
         raise DictError, message

   def SockIO(self):
      # verify we are connected first
      if self._connect:
         try:
            # try sending the data, if we recv it back, read from the
            # socket and print it, socket errors and select errors are
            # handled when tcp.SocketErorr, is raised, when Assertion
            # fails, then it is handled separately.
            sent = self.Write(self._cmdLine + "\r\n")
            if sent:
               print self.Read(self._ReadLength, self._ReadWait)
         except tcp.SocketError, message:
            self._error(message)
         except AssertionError, message:
            self._error(message)

   # --
   # Begin dictionary client handling, refer RFC2229
   def Define(self):
      if self._cmdLength == 3:
         self.SockIO()
      else:
         print "%s usage: " %self._cmd
         print " define [ name of the database ] word"
         print " databases can be a name or * sign or ! sign"

   def Match(self):
      if self._cmdLength == 4:
         self.SockIO()
      else:
         print "%s usage: " %self._cmd
         print " match [ name of the database ] [ a strategy ] word"

   def Show(self):
      if ((self._cmdLength <= 3) and (self._cmdLength > 1)):
         self.SockIO()
      else:
         print "%s usage: " %self._cmd
         print " show [ databases | strategies | server ]"
         print " show info [ name of the database ]"

   def Status(self):
      if self._cmdLength == 1:
         self.SockIO()

   def Help(self):
      if self._cmdLength == 1:
         self.SockIO()

   # End dictionary client handling
   # --

   # command line
   def Input(self, Prompt = None):
      prompt = None
      if Prompt:
         prompt = Prompt

      # We are in the loop, until we get EOF or signal (to which we are
      # signed up for, by the caller)
      flag = True
      while flag and self._connect:
         line = None
         try:
            prompt = "%s [%d]> " %(self._host, self._console.GetHistLength())
            line = self._console.raw_input(prompt)
         except EOFError:
            flag = False
            print
         except KeyboardInterrupt:
            print
            pass
         else:
            # If we have the line...
            if line:
               # we strip it
               self._cmdLine = line.strip()
               # split using shlex, gives almost close to shell handling of
               # inputs
               cmd = shlex.split(self._cmdLine)

               # calculate command line length
               self._cmdLength = len(cmd)

               # If we get errors on trying to lower the case of the 
               # cmd, we just print it, not error out and handle the 
               # cmds in function
               try:
                  self._cmd = cmd[0].lower()
               except IndexError, message:
                  print "invalid input"
               else:
                  if self._cmd == "define":
                     self.Define()
                  elif self._cmd == "match":
                     self.Match()
                  elif self._cmd == "show":
                     self.Show()
                  elif self._cmd == "status":
                     self.Status()
                  elif self._cmd == "quit":
                     flag = False
                  elif self._cmd == "help":
                     self.Help()
                  elif self._cmd == "exit":
                     flag = False
                  else:
                     print "invalid command: %s" %self._cmd

# the above class in one single function, used to get single word
# definitions
def GetWord(Host, Port, Word):
   retval = None
   if Host and Port:
      tcpClient = None
      try:
         tcpClient = tcp.tcp(Host, Port)
      except tcp.SocketError, message:
         raise DictError, message

      try:
         tcpClient.Connect()
      except tcp.SocketError, message:
         raise DictError, message

      if Word:
         info = "client info %s %s version 0.1\r\n" %(os.uname()[0],
               os.uname()[2])
         cmd = "define * %s\r\n" %Word.strip()
         sent = None
         try:
            sent = tcpClient.Write(info)
            if sent:
               print tcpClient.Read(1024, 2)
            sent = tcpClient.Write(cmd)
            if sent:
               retval = tcpClient.Read(1024, 2)
         except tcp.SocketError, message:
            raise DictError, message
         except AssertionError, message:
            raise DictError, message
         else:
            tcpClient.Close()
      else:
         tcpClient.Close()

   return retval
